EGT modelling of peer-feedback and genAI invasion

EGT modelling of cooperation in classroom with LLM

Modelling cooperation amongst peers

The model examines the relationship between peers in a learning environment providing feedback on each others work. In this model the agents are students which we will refer to as learners, and interactions are between learners choosing to provide and receive feedback from each other. We use \(P\) to indicate the learner providing feedback, and \(R\) the learner receiving feedback.

The strategy of interacting with other learners is outlined initially with a single parameter:

  • \(\Theta \in[0,1]\): Level of cooperative effort - that is how much effort a learner puts into providing feedback to another learner. The more effort towards cooperation the more benefit a peer receives from the feedback, and the more cost is incurred by the provider. \(\Theta_P\) indicates the collaborative effort of the learner providing the feedback, and \(\Theta_R\) the collaborative effort of the incoming feedback learner receiving the feedback.

The environment is balanced by three parameters that guide the payoffs of the different strategies:

  • \(c\) - Cost of cooperation
  • \(f\) - Benefit to yourself from others providing feedback / cooperating with you.
  • \(b\) - Benefit to yourself for providing others with feedback (i.e. you learn by providing feedback)
  • \(a\) - Benefit to yourself if you are cooperating with someone with the same strategy as you (i.e. a social benefit, gravitation towards similar learners).

The model initially explores the scenario \(c=4,f=5, b=2,a=1\), and will generally assume that \(f > c\). We explore varying strategies and varying the value of \(b\).

In this framework if a player with effort strategy \(\Theta_R\) receives feedback from someone with effort strategy \(\Theta_P\) they gain some perceived value, \(V(R|P)\) (read as value to reciever \(R\) when they meet provider \(P\)), out of sharing feedback with a peer:

\[V(R|P) = \Theta_P f + \Theta_R b - \Theta_Rc +a[RP]\]

Where \(a[RP]=a\) if \(\Theta_R=\Theta_P\), and zero otherwise. This amounts to a preference for working with like-minded peers. Alternatively \(a[RP]\) can specified as \(a(1-|\Theta_R-\Theta_P|)\), which is fine mathematically but still requires some thinking.

\[ V(R|P) = \Theta_P f + \Theta_R b - \Theta_Rc +a(1-|\Theta_R-\Theta_P|) \]

So the learner gains (or at least perceives to gain) value from the knowledge from the feedback at a level dependent on the providers effort (\(f\Theta_P\)), as well as from providing feedback to them depending their own effort towards cooperation (\(b\Theta_R\)). They incur a cost relative to the effort they put into cooperation (\(c\Theta_R\)).

Within this framework there are three strategies that agents can take that we will examine:

  • \(C\): Cooperation, where cooperative effort is highest, \(\Theta_C=1\)
  • \(T\): Token-effort, where cooperative effort is half-way, \(\Theta_T=0.5\)
  • \(F\): Free-rider, with no cooperative effort, \(\Theta_F=0\)

To introduce genAI we provide two new strategy parameters, indicating the use of genAI for providing feedback to others, or using genAI to supplement feedback a learner receives:

  • \(\Pi\in\{0,1\}\): The use of genAI to provide feedback (1) or not (0). \(\Pi_P\) indicates the choice of the provider of feedback.
  • \(\Gamma \in \{0,1\}\): The use of genAI to analyse your own work (1) or not (0). \(\Gamma_R\) indicates the choice of the receiver.

Additionally, there are two new environment parameters:

  • \(q\): The quality of the genAI for feedback.
  • \(k\): The reduced cost of providing feedback using AI.

We use two values, \(q=0.8\) to indicate slightly worse than what the peer would provide (with full effort), or \(q=1.25\) for slightly better than what you would expect from peers. We use \(k=0.1\) to indicate a low cost (compared to providing the feedback yourself).

This adds four new strategies of genAI use:

  • \(N\): No genAI use, \(\Pi=0,\Gamma=0\).
  • \(S\): Using genAI for only your own work, \(\Pi=0,\Gamma=1\). This incurs an additional cost \(c\times k\), but also provides a new benefit \(f \times q\). However, this benefit cancels out any benefit of receiving feedback from someone who is using genAI to produce the feedback. This means that when \(\Gamma_R=1\) and \(\Pi_P=1\) you can only get this benefit once.
  • \(O\): Using genAI only to provide feedback for others, \(\Pi=1, \Gamma = 0\). This reduces the cost of providing feedback to \(c \times k\) (as \(0<k<1\)), but also changes the value of the feedback to \(f \times q\) instead of \(f \times \Theta_P\).
  • \(B\): Using genAI for yourself and others, \(\Pi=1,\Gamma=1\). This combines the effects of \(S\) and \(O\).

We then combine the cooperation strategies and genAI strategies. So \(CN\) indicates that the learner is fully cooperating but not using genAI for themselves or for providing feedback. \(FS\) would indicate that a Free-rider (no effort towards cooperation) is using the genAI for their own feedback. Note that the strategies \(FO\) and \(FB\) do not make sense - a Free-rider is not providing feedback for others so would not bother using genAI for it. They might move towards \(TS\) or \(TB\) however.

The new, extended value calculation is more complicated:

\[V(R|P) = (1-\Gamma_R)[\Pi_P q f + (1-\Pi_P)\Theta_P f] \\ + \Gamma_R[q f-kc + (1-\Pi_P)\Theta_Pf] \\ +(1-\Pi_R)\Theta_R(b-c) \\ -\Pi_R k c + a(1-|\Theta_R-\Theta_P|)\]

Where \(a[RP]\) returns the value of \(a\) is \(\Pi_R=\Pi_P\) and \(\Theta_R=\Theta_P\), and zero otherwise.

It takes a bit of looking, but the above formula, for \(\Gamma_R=\Pi_R=\Pi_P=0\) this formula reduces to the non-genAI scenario (note that \(\Gamma_P\) is irrelevant for calculating the receivers payoff).

Playing with notation

It is worth exploring re-arranging the notation, and simplifying with \(\Gamma=1\) for all participants (this is assuming that everyone will use genAI for themselves).

Simplified model with \(\Gamma=1\)

\[V(R|P) = q f-kc + \\ (1-\Pi_P)\Theta_Pf + \Pi_P \Theta_P q f \\ +(1-\Pi_R)\Theta_R(b-c) \\ -\Pi_R k c + a(1-|\Theta_R-\Theta_P|)\]

Simplified model with \(\Gamma=1\), arranging for envirnoment parameters

\[V(R|P) = ((1-\Pi_P + q\Pi_P )\Theta_P + q)f \\ + (1-\Pi_R)\Theta_R b \\ - ((1 - \Pi_R + \Pi_R k)\Theta_R + k)c \\ + (1-|\Theta_R-\Theta_P|)a\]

Instantiating the game

The game is instatiated in the code below:

Show the code
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.0     ✔ tibble    3.2.1
✔ lubridate 1.9.3     ✔ tidyr     1.3.1
✔ purrr     1.0.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Show the code
# library(EvolutionaryGames)

get_payoff <- function(
        # Strat_r: Receiving strat, Strat_p: Providing strat
    Strat_r, Strat_p, # expects rows of data frame with columns Eff, AIP, AIS
    p = list(
        # Parameters
        c = 4, # cost
        f = 5, # receive benefit - c <> b_r might depend on knowledge gap??
        b = 2, # self benefit
        q = 0.8, # LLM is 0.8 of decent feedback
        k = 0.1, # reduces cost of giving fb by this
        a = 1
    )) {
    # Relabelling to match model forumula
    Theta_R = Strat_r$Eff
    Theta_P = Strat_p$Eff
    Gamma_R = Strat_r$AIS
    Pi_R = Strat_r$AIP
    Pi_P = Strat_p$AIP
    
    # V.Gamma_R  <-  Gamma_R * (p$q * p$f - p$k * p$c)
    # V.Pi_P <-  (1-Pi_P) * max(Theta_P + Gamma_R * p$q, Gamma_R * p$q) * p$f + (Pi_P * p$k * p$f) * (1 - Gamma_R)
    # V.Pi_R <-  (1-Pi_R) * Theta_R * (p$b - p$c) - Pi_R * p$k * p$c

    V.Gamma_R0 <- (1-Gamma_R) * (
        Pi_P * p$q * p$f + (1-Pi_P) * (Theta_P * p$f)
        )
    V.Gamma_R1 <- Gamma_R*(p$q * p$f - p$k*p$c + (1-Pi_P) * Theta_P * p$f)
    V.Pi_R0 <- (1 - Pi_R) * Theta_R * (p$b - p$c)
    V.Pi_R1 <- -Pi_R * p$k * p$c
    
    same_Pi_strat <- if_else(Pi_R == Pi_P, 1, 0)
    same_Theta_strat <- if_else(Theta_R == Theta_P, 1, 0)
    same_strat = same_Pi_strat * same_Theta_strat * p$a
    
    V = V.Gamma_R0 + V.Gamma_R1 + V.Pi_R0 + V.Pi_R1 + same_strat
    return(V)
}
Show the code
# game modelling
all_strategies <- tribble(
    ~Label, ~Eff, ~AIS, ~AIP,
    "CN", 1,   0, 0,
    "TN", 0.5, 0, 0,
    "FN", 0,   0, 0,
    "CS", 1,   1, 0,
    "TS", 0.5, 1, 0,
    "FS", 0,   1, 0,
    "CO", 1,   0, 1,
    "TO", 0.5, 0, 1,
    "FO", 0,   0, 1,
    "CB", 1,   1, 1,
    "TB", 0.5, 1, 1,
    "FB", 0,   1, 1
)
build_payoff_matrix <- function(
        S,
        # Parameters
        p = list(
            c = 2, # cost
            f = 2, # receive benefit
            b = 1, # self benefit
            q = 0.8, # LLM is 0.8 of decent feedback
            k = 0.1 # reduces cost of giving fb by this, but also reduces self benefit
        )
) {
    N_strategies <- nrow(S)
    payoffs <- array(rep(NA_real_, N_strategies*N_strategies), dim = c(N_strategies, N_strategies)) 
    for (R in 1:N_strategies) {
        for (P in 1:N_strategies) {
            payoffs[R,P] <- get_payoff(slice(S, R), slice(S, P), p)
        }
    }
    return(payoffs)
}

print_payoff_matrix <- function(M, stgys) {
    rownames(M) <- stgys
    colnames(M) <- stgys
    print(M)
}

fetch_payoff_matrix <- function(strategies, p, as_df = FALSE,
                                add_exp_V = FALSE) {
    # strategies: character vector, p: list
    S.df <- tibble(Label = strategies) |> # preserves order of strategies
        inner_join(all_strategies, by = "Label")
    payoff_matrix <- build_payoff_matrix(S.df, p)
    if (as_df) {
        colnames(payoff_matrix) <- strategies
        payoff_df <- as_tibble(payoff_matrix) |> 
            mutate(`Payoff to strategy:` = strategies) |> 
            select(`Payoff to strategy:`, everything())
        if (add_exp_V) {
            payoff_df <- payoff_df |> 
                rowwise() |> 
                mutate(ExpV = mean(c_across(where(is.numeric))))
        }
        return(payoff_df)
    } else {
        return(payoff_matrix)
    }
}

No AI

Comparing Free-rider, Token (half effort) and Cooperator.

Initially with \(c=4,f=5,b=2, a=1\).

Show the code
# Model params
        p = list(
            c = 4, # cost
            f = 5, # receive benefit
            b = 2, # self benefit
            q = 0.8, # LLM is 0.8 of decent feedback
            k = 0.1, # reduces cost of giving fb by this, but also reduces self benefit
            a = 1 # assortment
        )
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 2
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1

Comparing just \(FN\) and \(CN\) (i.e. Hawk-Dove)

Show the code
strategies <- c("CN", "FN")
fetch_payoff_matrix(strategies, p, as_df = T)
# A tibble: 2 × 3
  `Payoff to strategy:`    CN    FN
  <chr>                 <dbl> <dbl>
1 CN                        4    -2
2 FN                        5     1

The overall fitness of the population is given, for proportion of \(p\) cooperators, by \(W(p)=2p^2+2p(1-p)=2p\), so we want to have \(p\) as high as possible. But from the point of view of the individual it is much nicer to choose \(FN\).

We can also include TN to see how the dynamics evolve over time.

Show the code
strategies <- c("TN", "CN", "FN")
payoffs_df <- fetch_payoff_matrix(strategies, p, as_df = T)
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
payoffs_df
# A tibble: 3 × 4
  `Payoff to strategy:`    TN    CN    FN
  <chr>                 <dbl> <dbl> <dbl>
1 TN                      2.5     4    -1
2 CN                      0.5     4    -2
3 FN                      2.5     5     1
Show the code
strategies <- c("T", "C", "F")
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()

From this we can see that a population evenly mixed between the three strategies (in the centre of the triangle) would move away from the cooperative strategy \(CN\) and slowly veer towards a population of free-riders, \(FN\).

Introducing AI

Three plots each for lower AI quality and higher AI quality. Many combos, so assumptions about behaviour:

Show the code
fetch_payoff_matrix(
    all_strategies$Label, p, as_df = T, add_exp_V = T) |> arrange(ExpV)
# A tibble: 12 × 14
# Rowwise: 
   `Payoff to strategy:`    CN    TN    FN    CS    TS    FS    CO    TO    FO
   <chr>                 <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1 CN                      4     0.5  -2     4     0.5  -2     2     2     2  
 2 TN                      4     2.5  -1     4     2.5  -1     3     3     3  
 3 CS                      7.6   4.1   1.6   7.6   4.1   1.6   1.6   1.6   1.6
 4 CO                      4.6   2.1  -0.4   4.6   2.1  -0.4   4.6   3.6   3.6
 5 TO                      4.6   2.1  -0.4   4.6   2.1  -0.4   3.6   4.6   3.6
 6 FO                      4.6   2.1  -0.4   4.6   2.1  -0.4   3.6   3.6   4.6
 7 FN                      5     2.5   1     5     2.5   1     4     4     4  
 8 TS                      7.6   6.1   2.6   7.6   6.1   2.6   2.6   2.6   2.6
 9 CB                      8.2   5.7   3.2   8.2   5.7   3.2   4.2   3.2   3.2
10 TB                      8.2   5.7   3.2   8.2   5.7   3.2   3.2   4.2   3.2
11 FB                      8.2   5.7   3.2   8.2   5.7   3.2   3.2   3.2   4.2
12 FS                      8.6   6.1   4.6   8.6   6.1   4.6   3.6   3.6   3.6
# ℹ 4 more variables: CB <dbl>, TB <dbl>, FB <dbl>, ExpV <dbl>
  • For any strategy \(X\), \(E[V(XS)]>E[V(XO)]>E[V(XN)]\), so genAI strategies of none, \(XN\), or others only, \(XO\), are ignored. This is the high cost benefit ratio of using the genAI for your own feedback. Maybe a supplementary proof??
  • \(FB=TB=CB\), so these will be treated as \(FB\). Note that we have used \(FB\) as this is not really putting in effort to feedback - the genAI is being used to provide the feedback.
Show the code
interesting_strategies <- all_strategies |> 
    filter(!str_detect(Label, "O|N"), 
           Label != "TB", Label != "CB") |> 
    arrange(Label) |> 
    pull(Label)
interesting_strategies
[1] "CS" "FB" "FS" "TS"

This leaves 4: ,

Show the code
fetch_payoff_matrix(
    interesting_strategies, p, as_df = T, add_exp_V = T) |> arrange(ExpV)
# A tibble: 4 × 6
# Rowwise: 
  `Payoff to strategy:`    CS    FB    FS    TS  ExpV
  <chr>                 <dbl> <dbl> <dbl> <dbl> <dbl>
1 CS                      7.6   1.6   1.6   4.1  3.72
2 TS                      7.6   2.6   2.6   6.1  4.72
3 FB                      8.2   4.2   3.2   5.7  5.32
4 FS                      8.6   3.6   4.6   6.1  5.72

Increasing self benefit in no-AI environment

No AI. \(c>b=2\)

Parameters:

Show the code
p$b <- 2
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 2
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1
Show the code
strategies <- c("TN", "CN", "FN")
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           # .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()

No AI \(c=b=4\)

Parameters:

Show the code
p$b <- 4
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 4
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1
Show the code
strategies <- c("TN", "CN", "FN")
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           # .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()

No AI \(c<b=6\)

Parameters:

Show the code
p$b <- 6
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 6
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1
Show the code
strategies <- c("TN", "CN", "FN")
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           # .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()

Increasing self benefit in genAI environment

With AI: \(c>b=2\)

Parameters:

Show the code
p$q <- 0.8
p$b <- 2
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 2
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1
Show the code
strategies <- c("FB", "CS", "FS")
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           # .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()

With AI: FS, FB, CS. \(c=b=4\)

Parameters:

Show the code
p$q <- 0.8
p$b <- 4
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 4
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1
Show the code
strategies <- c("FB", "CS", "FS")
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           # .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()

With AI: FS, FB, CS. \(c<b=6\)

Parameters:

Show the code
p$q <- 0.8
p$b <- 6
glimpse(p)
List of 6
 $ c: num 4
 $ f: num 5
 $ b: num 6
 $ q: num 0.8
 $ k: num 0.1
 $ a: num 1
Show the code
strategies <- c("FB", "CS", "FS")
payoffs_M <- fetch_payoff_matrix(strategies, p, as_df = F)
Show the code
import numpy as np
import matplotlib.pyplot as plt
from egttools.plotting.simplified import plot_replicator_dynamics_in_simplex, plot_pairwise_comparison_rule_dynamics_in_simplex_without_roots
from egttools.utils import calculate_stationary_distribution

payoffs = np.array(r.payoffs_M)
type_labels = r.strategies

fig, ax = plt.subplots(figsize=(15,10))
simplex, gradient_function, roots, roots_xy, stability = plot_replicator_dynamics_in_simplex(payoffs, nb_of_initial_points_for_root_search=100, 
ax=ax)

plot = (simplex.add_axis(ax=ax)
           .draw_triangle()
           .draw_gradients(zorder=0)
           # .add_colorbar()
           .add_vertex_labels(type_labels)
           .draw_stationary_points(roots_xy, stability))

ax.axis('off')
(np.float64(0.0), np.float64(1.0), np.float64(0.0), np.float64(0.9093266739736605))
Show the code
ax.set_aspect('equal')
plt.xlim((-.05,1.05))
(-0.05, 1.05)
Show the code
plt.ylim((-.02, simplex.top_corner + 0.05))
(-0.02, 0.9160254037844386)
Show the code
plt.show()